package com.simple.quotes.loading;

import com.simple.quotes.core.Quote;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Загрузка файла с котировками
 * @author Petro
 */
public class QuoteLoader
{
    private static final Logger log = Logger.getLogger(QuoteLoader.class);
    private static final String SEPARATOR = ",";
    private static final Pattern FORMAT_PATTERN = Pattern.compile("<\\S*>"+SEPARATOR+"<\\S*>"+SEPARATOR+"<\\S*>");
    private static final String DEFAULT_FORMAT = "<TICKER>,<PER>,<DATE>,<TIME>,<OPEN>,<HIGH>,<LOW>,<CLOSE>";
    private static Map<String, Integer> fileFormat = new HashMap<String, Integer>(); //формат строк в виде <Имя, позиция>
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd" + "HHmmss");
    private static List<Quote> quoteList = new ArrayList<Quote>(); //собственно данные
    private static String[] fields; //служебная переменная

    /**
     * Загрузка данных из файла file в статический список котировок quoteList.
     * Из первой строки считывается формат данных; если формат не задан, то используется формат по умолчанию.
     * @param file файл
     * @throws QuoteLoadingException Если произошла ошибка при открытии/чтении/закрытии файла или некорректное содержимое файла
     */
    public static void load(File file) throws QuoteLoadingException
    {
        log.info("Loading quotes from file " + file.getAbsolutePath());
        Scanner scanner = null;
        quoteList.clear();
        try
        {
            scanner = new Scanner(new FileReader(file));
            String line;

            if (scanner.hasNextLine()) {
                line = scanner.nextLine(); //чтение первой строки файла
                if(FORMAT_PATTERN.matcher(line).matches()) { //если формат указан в первой строке файла
                    setFormat(line);
                    //считываем первую строку с данными и запоминаем в static тикер и период
                    if (scanner.hasNextLine()) {
                        readFirstDataLine(scanner.nextLine());
                    } else { //если нет ни одной строки с данными, то выходим
                        log.error("Data is empty.");
                        return;
                    }
                } else { //если формат не указан, используем формат по умолчанию
                    log.warn("File format not specified. Default format will be used.");
                    setFormat(DEFAULT_FORMAT);
                    //прочитанная ранее строка это уже строка с данными. запоминаем в static тикер и период
                    readFirstDataLine(line);
                }
            } else { //если нет даже первой строки, то выходим
                log.error("File is empty.");
                return;
            }

            //считываем остальные строки
            while (scanner.hasNextLine()) {
                readLine(scanner.nextLine());
            }

            if (!quoteList.isEmpty()) {
                log.info(String.format("Quotes successfully loaded. Period: %1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS - %2$tY.%2$tm.%2$td %2$tH:%2$tM:%2$tS Step = %3$s%n",
                        quoteList.get(0).getDateTime(), quoteList.get(quoteList.size()-1).getDateTime(), Quote.getPeriod()));
            }
        } catch (IOException e) {
            log.error("Error opening file!", e);
            throw new QuoteLoadingException("Error opening file!", e);
        } catch (QuoteLoadingException e) {
            log.error(e.getMessage(), e);
            throw new QuoteLoadingException(e.getMessage(), e);
        } finally {
            if (scanner != null) {
                scanner.close();
                IOException closeException = scanner.ioException();
                if (closeException != null) {
                    log.error("Error closing file!", closeException);
                    throw new QuoteLoadingException("Error closing file!", closeException);
                }
            }
        }
    }

    public static List<Quote> getList() {
        return quoteList;
    }

    /**
     * Установка формата считываемых строк
     * @param line Строка вида <TICKER>,<PER>,<DATE>,<TIME>,<OPEN>,<HIGH>,<LOW>,<CLOSE>
     */
    private static void setFormat(String line) {
        log.info("File format is: " + line);
        line = line.trim();
        fields = line.split(SEPARATOR);
        for(int i = 0; i < fields.length; ++i)
        {
            //угловые скобки не учитываем
            String field = fields[i].substring(1, fields[i].length()-1);
            fileFormat.put(field, i);
        }
    }

    /**
     * Читает первую строку с данными, чтобы достать из нее константы - ticker и period
     * @param line строка с данными
     * @throws QuoteLoadingException Если строка данных не соответствуют формату или произошло какое-либо исключение при чтении
     */
    private static void readFirstDataLine(String line) throws QuoteLoadingException {
        fields = line.split(SEPARATOR);
        if(fields.length != fileFormat.size())
            throw new QuoteLoadingException("Data does not match format!");

        Quote.setTicker(getField(Field.TICKER.name()));
        Quote.setPeriod(getField(Field.PER.name()));
        //читаем оставшуюся часть строки
        readLine(line);
    }


    /**
     * Читает строку с данными
     * @param line строка с данными
     * @throws QuoteLoadingException Если строка данных не соответствуют формату или произошло какое-либо исключение при чтении
     */
    private static void readLine(String line) throws QuoteLoadingException{
        Calendar calendar = Calendar.getInstance();
        try {
            fields = line.split(SEPARATOR);
            if(fields.length != fileFormat.size())
                throw new QuoteLoadingException("Data does not match format!");
            
            Quote quote = new Quote();
            calendar.setTime(simpleDateFormat.parse(getField(Field.DATE.name()) + getField(Field.TIME.name())));
            quote.setDateTime(calendar);
            quote.setOpen(Double.valueOf(getField(Field.OPEN.name())));
            quote.setHigh(Double.valueOf(getField(Field.HIGH.name())));
            quote.setLow(Double.valueOf(getField(Field.LOW.name())));
            quote.setClose(Double.valueOf(getField(Field.CLOSE.name())));
            quote.setVolatility(Double.valueOf(getField(Field.VOL.name())));

            quoteList.add(quote);
        } catch(ParseException e) {
            throw new QuoteLoadingException("Error parsing file!", e);
        } catch(Exception e){
            throw new QuoteLoadingException("Error reading file!", e);
        }
    }

    /**
     * Достает значение поля из служебной переменной fields
     * @param fieldName имя поля
     * @return значение поля
     */
    private static String getField(String fieldName)
    {
        Integer fieldIndex = fileFormat.get(fieldName);
        return fieldIndex == null ? "0" : fields[fieldIndex];
    }
}
